﻿/*
 * Released under Microsoft Public License (Ms-PL) 
 * Sponsored by Development Platform Evangelism unit of Microsoft Israel
 * 
 * Copyright © 2008 by Tamir Khason
 * http://blogs.microsoft.co.il/blogs/tamir/
 * http://sharpsoft.net/
 * 
 * More information including licensing and term of use
 * can be found on http://www.codeplex.com/SilverlightRTL/
 * 
 * Diff method is derived from Matthias Hertel implementation of
 * "An O(ND) Difference Algorithm and its Variations" by Eugene Myers 
 * http://www.mathertel.de/
 */

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections;
using System.Reflection;
using System.Text;

namespace System.Windows.BidiControls
{
    public static class Utils
    {
        /// <summary>
        /// Extension method to find differences between two strings
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        public static Differences<char> Diff(this string source, string target)
        {
            Differences<char> res = new Differences<char>();
            res.Source = source;
            res.Target = target;
            int max = source.Length + target.Length + 1;
            int[] startVector = new int[2 * max + 2];
            int[] endVector = new int[2 * max + 2];

            stringLCS(source, 0, source.Length, target, 0, target.Length, startVector, endVector, ref res);

            res.SourceDifferences.RemoveAt(itm => { return itm.Value == (char)0; });
            res.TargetDifferences.RemoveAt(itm => { return itm.Value == (char)0; });

            return res;
        }

        private static void stringLCS(string strA, int startA, int endA, string strB, int startB, int endB, int[] startVector, int[] endVector, ref Differences<char> result)
        {
            while (startA < endA && startB < endB && strA[startA] == strB[startB]) { startA++; startB++; }
            while (startA < endA && startB < endB && strA[endA - 1] == strB[endB - 1]) { --endA; --endB; }

            if (startA == endA)
            {
                while (startB < endB)
                {
                    Difference<char> d = new Difference<char>();
                    d.Index = startB++;
                    d.Value = strB[d.Index];
                    result.TargetDifferences.Add(d);
                }
            }
            else if (startB == endB)
            {
                while (startA < endA)
                {
                    Difference<char> d = new Difference<char>();
                    d.Index = startA++;
                    d.Value = strA[d.Index];
                    result.SourceDifferences.Add(d);
                }
            }
            else
            {
                int a, b;
                stringSMS(strA, startA, endA, strB, startB, endB, startVector, endVector, out a, out b);
                stringLCS(strA, startA, a, strB, startB, b, startVector, endVector, ref result);
                stringLCS(strA, a, endA, strB, b, endB, startVector, endVector, ref result);
            }

        }

        private static void stringSMS(string strA, int startA, int endA, string strB, int startB, int endB, int[] startVector, int[] endVector, out int a, out int b)
        {
            a = b = 0;
            int max = strA.Length + strB.Length + 1;

            int startK = startA - startB;
            int endK = endA - endB;

            int delta = (endA - startA) - (endB - startB);
            bool oddDelta = (delta & 1) != 0;

            int startOffset = max - startK;
            int endOffset = max - endK;

            int maxD = ((endA - startA + endB - startB) / 2) + 1;

            startVector[startOffset + startK + 1] = startA;
            endVector[endOffset + endK - 1] = endA;

            for (int D = 0; D <= maxD; D++)
            {

                for (int k = startK - D; k <= startK + D; k += 2)
                {
                    int x, y;
                    if (k == startK - D)
                    {
                        x = startVector[startOffset + k + 1];
                    }
                    else
                    {
                        x = startVector[startOffset + k - 1] + 1;
                        if ((k < startK + D) && (startVector[startOffset + k + 1] >= x))
                            x = startVector[startOffset + k + 1];
                    }
                    y = x - k;

                    while ((x < endA) && (y < endB) && (strA[x] == strB[y]))
                    {
                        x++; y++;
                    }
                    startVector[startOffset + k] = x;

                    if (oddDelta && (endK - D < k) && (k < endK + D))
                    {
                        if (endVector[endOffset + k] <= startVector[startOffset + k])
                        {
                            a = startVector[startOffset + k];
                            b = startVector[startOffset + k] - k;
                            return;
                        }
                    }

                }

                for (int k = endK - D; k <= endK + D; k += 2)
                {
                    int x, y;
                    if (k == endK + D)
                    {
                        x = endVector[endOffset + k - 1];
                    }
                    else
                    {
                        x = endVector[endOffset + k + 1] - 1;
                        if ((k > endK - D) && (endVector[endOffset + k - 1] < x))
                            x = endVector[endOffset + k - 1];
                    }
                    y = x - k;

                    while ((x > startA) && (y > startB) && (strA[x - 1] == strB[y - 1]))
                    {
                        x--; y--;
                    }
                    endVector[endOffset + k] = x;

                    if (!oddDelta && (startK - D <= k) && (k <= startK + D))
                    {
                        if (endVector[endOffset + k] <= startVector[startOffset + k])
                        {
                            a = startVector[startOffset + k];
                            b = startVector[startOffset + k] - k;
                            return;
                        }
                    }

                }

            }

            throw new OverflowException();
        }

        /// <summary>
        /// Extended method allows to clone Silverlight object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static T Clone<T>(this T source) where T : DependencyObject
        {
            Type t = source.GetType();
            T no = (T)Activator.CreateInstance(t);

            Type wt = t;
            while (wt.BaseType != typeof(DependencyObject))
            {
                FieldInfo[] fi = wt.GetFields(BindingFlags.Static | BindingFlags.Public);
                for (int i = 0; i < fi.Length; i++)
                {
                    {
                        DependencyProperty dp = fi[i].GetValue(source) as DependencyProperty;
                        if (dp != null && fi[i].Name != "NameProperty")
                        {
                            DependencyObject obj = source.GetValue(dp) as DependencyObject;
                            if (obj != null)
                            {
                                object o = obj.Clone();
                                no.SetValue(dp, o);
                            }
                            else
                            {
                                if (fi[i].Name != "CountProperty" &&
                                    fi[i].Name != "GeometryTransformProperty" &&
                                    fi[i].Name != "ActualWidthProperty" &&
                                    fi[i].Name != "ActualHeightProperty" &&
                                    fi[i].Name != "MaxWidthProperty" &&
                                    fi[i].Name != "MaxHeightProperty" &&
                                    fi[i].Name != "StyleProperty")
                                {
                                    no.SetValue(dp, source.GetValue(dp));
                                }

                            }
                        }
                    }
                }
                wt = wt.BaseType;
            }

            PropertyInfo[] pis = t.GetProperties();
            for (int i = 0; i < pis.Length; i++)
            {

                if (
                    pis[i].Name != "Name" &&
                    pis[i].Name != "Parent" &&
                    pis[i].CanRead && pis[i].CanWrite &&
                    !pis[i].PropertyType.IsArray &&
                    !pis[i].PropertyType.IsSubclassOf(typeof(DependencyObject)) &&
                    pis[i].GetIndexParameters().Length == 0 &&
                    pis[i].GetValue(source, null) != null
                    )
                    pis[i].SetValue(no, pis[i].GetValue(source, null), null);
                else if (pis[i].PropertyType.GetInterface("IList", true) != null)
                {
                    int cnt = (int)pis[i].PropertyType.InvokeMember("get_Count", BindingFlags.InvokeMethod, null, pis[i].GetValue(source, null), null);
                    for (int c = 0; c < cnt; c++)
                    {
                        object val = pis[i].PropertyType.InvokeMember("get_Item", BindingFlags.InvokeMethod, null, pis[i].GetValue(source, null), new object[] { c });

                        object nVal = val;
                        DependencyObject v = val as DependencyObject;
                        if (v != null)
                            nVal = v.Clone();
                        if (pis[i].GetValue(no, null) == null)
                        {
                            object obj = Activator.CreateInstance(pis[i].PropertyType);
                            pis[i].SetValue(no, obj, null);
                        }
                        pis[i].PropertyType.InvokeMember("Add", BindingFlags.InvokeMethod, null, pis[i].GetValue(no, null), new object[] { nVal });
                    }
                }
            }

            if (t.GetInterface("IList", true) != null)
            {
                int cnt = (int)t.InvokeMember("get_Count", BindingFlags.InvokeMethod, null, source, null);
                for (int c = 0; c < cnt; c++)
                {
                    object val = t.InvokeMember("get_Item", BindingFlags.InvokeMethod, null, source, new object[] { c });

                    object nVal = val;
                    DependencyObject v = val as DependencyObject;
                    if (v != null)
                        nVal = v.Clone();
                    if (no == null)
                    {
                        object obj = Activator.CreateInstance(t);
                        no = (T)obj;
                    }
                    object a = t.InvokeMember("Add", BindingFlags.InvokeMethod, null, no, new object[] { nVal });
                }
            }




            return no;
        }

        /// <summary>
        /// Extension method to remove element of type T, from source array, based on predictate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="srs"></param>
        /// <param name="predictate"></param>
        public static void RemoveAt<T>(this IList<T> srs, Predicate<T> predictate)
        {
            List<int> indices = new List<int>();
            for (int i = 0; i < srs.Count; i++)
            {
                if (predictate(srs[i]))
                {
                    indices.Add(i);
                }
            }
            for (int i = 0; i < indices.Count; i++)
            {
                srs.RemoveAt(indices[i]);
            }
        }

        public static int CountVisibleLetter(this string srs)
        {
            int c = 0;
            for (int i = 0; i < srs.Length; i++)
            {
                NBidi.BidiCharacterType ct = NBidi.UnicodeCharacterDataResolver.GetBidiCharacterType(srs[i]);
                NBidi.UnicodeGeneralCategory cc = NBidi.UnicodeCharacterDataResolver.GetUnicodeGeneralCategory(srs[i]);
                if (ct != NBidi.BidiCharacterType.NSM && cc != NBidi.UnicodeGeneralCategory.Lm)
                    c++;
            }
            return c;
        }

        internal static void PrintCharsDebug(this string source)
        {
             System.Diagnostics.Debug.WriteLine("----------------Source--------------");
             System.Diagnostics.Debug.WriteLine(source);
            for (int i = 0; i < source.Length; i++)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("{0}: {1} - {2}",i,source[i],(int)source[i]));
            }
            System.Diagnostics.Debug.WriteLine("------------------EOP----------------");
             
        }

        internal static void PrintDebug(string format, params object[] values)
        {
            PrintDebug(string.Format(format, values));
        }

        internal static void PrintDebug(string message)
        {
            System.Diagnostics.Debug.WriteLine(string.Format("\n---------{0}---------", DateTime.Now.ToString("hh:mm:ss.fff")));
            System.Diagnostics.Debug.WriteLine(message);
            System.Diagnostics.Debug.WriteLine("--------------EOP-------------\n");
        }

        internal static void PrintDebug(object o)
        {
            PrintDebug(o.ToString());
        }

        internal static string ClipboardData;
        

        /// <summary>
        /// Extension method to get pseudo clipboard data out of TextBox
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="origString"></param>
        public static void GetClipboard(this System.Windows.Controls.TextBox tb, string origString)
        {
            if (tb.SelectionLength > 0)
            {
                int sb = tb.SelectionStart;
                int sl = tb.SelectionLength;
                List<KeyValuePair<int,char>> tmp = new List<KeyValuePair<int,char>>();//TODO: this should be according the original string - char number
                for (int i = sb; i < sb+sl; i++)
                {
                    int idx = NBidi.NBidi.VisualToLogicalPosition(origString, i);
                    tmp.Add(new KeyValuePair<int, char>(idx, origString[idx - 1]));
                }

                tmp.Sort((Comparison<KeyValuePair<int, char>>)delegate(KeyValuePair<int, char> s, KeyValuePair<int, char> t)
                { return s.Key.CompareTo(t.Key); });

                StringBuilder b = new StringBuilder();
                for (int i = 0; i < tmp.Count; i++)
                {
                    b.Append(tmp[i].Value);
                }
                ClipboardData = b.ToString();
            }
        }

        /// <summary>
        /// Sets data from pseudo clipboard to the textbox
        /// </summary>
        /// <param name="tb"></param>
        public static void SetClipboard(this System.Windows.Controls.TextBox tb)
        {
            if (!string.IsNullOrEmpty(ClipboardData))
            {
                if (!string.IsNullOrEmpty(tb.SelectedText))
                {
                    tb.Text = tb.Text.Remove(tb.SelectionStart, tb.SelectionLength);
                }

                tb.Text = tb.Text.Insert(tb.SelectionStart, ClipboardData);
            }
        }

        
    }

    /// <summary>
    /// Utilization class holds two arrays of difference objects
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Differences<T> where T : IEquatable<T>
    {
        public List<Difference<T>> SourceDifferences = new List<Difference<T>>();
        public List<Difference<T>> TargetDifferences = new List<Difference<T>>();
        public object Source;
        public object Target;

        public readonly static Differences<T> None;

        internal void PrintDebug()
        {
            System.Diagnostics.Debug.WriteLine("--------------Source--------------");
            System.Diagnostics.Debug.WriteLine(Source.ToString());
            for (int i = 0; i < SourceDifferences.Count; i++)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("{0} - {1}",SourceDifferences[i].Index,SourceDifferences[i].Value));
            }
            System.Diagnostics.Debug.WriteLine("--------------Target--------------");
            System.Diagnostics.Debug.WriteLine(Target.ToString());
            for (int i = 0; i < TargetDifferences.Count; i++)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("{0} - {1}", TargetDifferences[i].Index, TargetDifferences[i].Value));
            }
            System.Diagnostics.Debug.WriteLine("----------------EOP---------------");
        }
    }

    /// <summary>
    /// Difference object
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public struct Difference<T> where T : IEquatable<T>
    {
        public int Index;
        public T Value;
    }
}
